--- /dev/null
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 2016 Benjamin Otte <otte@gnome.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gtkdebugupdatesprivate.h"
+
+/* duration before we start fading in us */
+#define GDK_DRAW_REGION_MIN_DURATION 50 * 1000
+/* duration when fade is finished in us */
+#define GDK_DRAW_REGION_MAX_DURATION 200 * 1000
+
+typedef struct {
+ gint64 timestamp;
+ cairo_region_t *region;
+} GtkDebugUpdate;
+
+static gboolean _gtk_debug_updates_enabled = FALSE;
+
+static GQuark _gtk_debug_updates_quark = 0;
+
+static void
+gtk_debug_updates_init (void)
+{
+ static gboolean inited = FALSE;
+
+ if (G_LIKELY (inited))
+ return;
+
+ _gtk_debug_updates_quark = g_quark_from_static_string ("-gtk-debug-updates");
+
+ inited = TRUE;
+}
+
+gboolean
+gtk_debug_updates_get_enabled (void)
+{
+ return _gtk_debug_updates_enabled;
+}
+
+void
+gtk_debug_updates_set_enabled (gboolean enabled)
+{
+ if (enabled)
+ gtk_debug_updates_init ();
+
+ _gtk_debug_updates_enabled = enabled;
+}
+
+gboolean
+gtk_debug_updates_get_enabled_for_display (GdkDisplay *display)
+{
+ if (gtk_debug_updates_get_enabled ())
+ return TRUE;
+
+ if (_gtk_debug_updates_quark == 0)
+ return FALSE;
+
+ return g_object_get_qdata (G_OBJECT (display), _gtk_debug_updates_quark) != NULL;
+}
+
+void
+gtk_debug_updates_set_enabled_for_display (GdkDisplay *display,
+ gboolean enabled)
+{
+ if (enabled)
+ {
+ gtk_debug_updates_init ();
+
+ g_object_set_qdata (G_OBJECT (display), _gtk_debug_updates_quark, GINT_TO_POINTER (TRUE));
+ }
+ else
+ {
+ if (_gtk_debug_updates_quark != 0)
+ g_object_steal_qdata (G_OBJECT (display), _gtk_debug_updates_quark);
+ }
+}
+
+static void
+gtk_debug_update_free (gpointer data)
+{
+ GtkDebugUpdate *region = data;
+
+ cairo_region_destroy (region->region);
+ g_slice_free (GtkDebugUpdate, region);
+}
+
+static void
+gtk_debug_update_free_queue (gpointer data)
+{
+ GQueue *queue = data;
+
+ g_queue_free_full (queue, gtk_debug_update_free);
+}
+
+static void
+gtk_debug_updates_print (GQueue *queue,
+ const cairo_region_t *region,
+ const char *format,
+ ...) G_GNUC_PRINTF(3,4);
+static void
+gtk_debug_updates_print (GQueue *queue,
+ const cairo_region_t *region,
+ const char *format,
+ ...)
+{
+#if 0
+ GString *string = g_string_new (NULL);
+ va_list args;
+
+ g_string_append_printf (string, "%3u: ", g_queue_get_length (queue));
+
+ if (region)
+ {
+ cairo_rectangle_int_t extents;
+
+ cairo_region_get_extents (region, &extents);
+ g_string_append_printf (string, "{%u,%u,%u,%u}(%u) ",
+ extents.x, extents.y,
+ extents.width, extents.height,
+ cairo_region_num_rectangles (region));
+ }
+
+ va_start (args, format);
+ g_string_append_vprintf (string, format, args);
+ va_end (args);
+
+ g_print ("%s\n", string->str);
+
+ g_string_free (string, TRUE);
+#endif
+}
+
+static gboolean
+gtk_window_manage_updates (GtkWidget *widget,
+ GdkFrameClock *frame_clock,
+ gpointer user_data)
+{
+ GQueue *updates;
+ GtkDebugUpdate *draw;
+ cairo_region_t *region;
+ gint64 timestamp;
+ GList *l;
+
+ updates = g_object_get_qdata (G_OBJECT (widget), _gtk_debug_updates_quark);
+ if (updates == NULL)
+ return FALSE;
+ timestamp = gdk_frame_clock_get_frame_time (frame_clock);
+
+ gtk_debug_updates_print (updates, NULL, "Managing updates");
+ /* First queue an update for all regions.
+ * While doing so, set the correct timestamp on all untimed regions */
+ region = cairo_region_create ();
+ for (l = g_queue_peek_head_link (updates); l != NULL; l = l->next)
+ {
+ draw = l->data;
+
+ if (draw->timestamp == 0)
+ {
+ draw->timestamp = timestamp;
+ gtk_debug_updates_print (updates, draw->region, "Setting timestamp to %lli\n", (long long) timestamp);
+ }
+
+ cairo_region_union (region, draw->region);
+ }
+ gtk_debug_updates_print (updates, region, "Queued update");
+ gdk_window_invalidate_region (gtk_widget_get_window (widget), region, TRUE);
+ cairo_region_destroy (region);
+
+ /* Then remove all outdated regions */
+ do {
+ draw = g_queue_peek_tail (updates);
+
+ if (draw == NULL)
+ {
+ gtk_debug_updates_print (updates, NULL, "Empty, no more updates");
+ g_object_set_qdata (G_OBJECT (widget), _gtk_debug_updates_quark, NULL);
+ return FALSE;
+ }
+
+ if (draw->timestamp + GDK_DRAW_REGION_MAX_DURATION >= timestamp)
+ break;
+
+ gtk_debug_updates_print (updates, draw->region, "Popped region");
+ draw = g_queue_pop_tail (updates);
+ gtk_debug_update_free (draw);
+ } while (TRUE);
+
+ return TRUE;
+}
+
+void
+gtk_debug_updates_add (GtkWidget *widget,
+ const cairo_region_t *region)
+{
+ GQueue *updates;
+ GtkDebugUpdate *draw;
+
+ if (!gtk_debug_updates_get_enabled_for_display (gtk_widget_get_display (widget)))
+ return;
+
+ updates = g_object_get_qdata (G_OBJECT (widget), _gtk_debug_updates_quark);
+ if (updates == NULL)
+ {
+ updates = g_queue_new ();
+ gtk_widget_add_tick_callback (widget,
+ gtk_window_manage_updates,
+ NULL,
+ NULL);
+ g_object_set_qdata_full (G_OBJECT (widget),
+ _gtk_debug_updates_quark,
+ updates,
+ gtk_debug_update_free_queue);
+ gtk_debug_updates_print (updates, NULL, "Newly created");
+ }
+ else
+ {
+ GtkDebugUpdate *first = g_queue_peek_head (updates);
+
+ if (first->timestamp == 0)
+ {
+ cairo_region_union (first->region, region);
+ gtk_debug_updates_print (updates, first->region, "Added to existing region");
+ return;
+ }
+ }
+
+ draw = g_slice_new0 (GtkDebugUpdate);
+ draw->region = cairo_region_copy (region);
+ g_queue_push_head (updates, draw);
+ gtk_debug_updates_print (updates, draw->region, "Added new region");
+}
+
+static void
+gtk_debug_updates_queue_get_extents (GQueue *updates,
+ GdkRectangle *extents)
+{
+ GtkDebugUpdate *draw;
+ GdkRectangle rect;
+ GList *l;
+
+ *extents = (GdkRectangle) { 0, 0, 0, 0 };
+
+ for (l = g_queue_peek_head_link (updates); l != NULL; l = l->next)
+ {
+ draw = l->data;
+
+ cairo_region_get_extents (draw->region, &rect);
+ gdk_rectangle_union (extents, &rect, extents);
+ }
+}
+
+GskRenderNode *
+gtk_debug_updates_get_render_node (GtkWidget *widget,
+ GskRenderer *renderer)
+{
+ GQueue *updates;
+ GskRenderNode *node;
+ GtkDebugUpdate *draw;
+ GdkRectangle rect;
+ gint64 timestamp;
+ double progress;
+ cairo_t *cr;
+ GList *l;
+
+ if (!gtk_debug_updates_get_enabled_for_display (gtk_widget_get_display (widget)))
+ return NULL;
+
+ updates = g_object_get_qdata (G_OBJECT (widget), _gtk_debug_updates_quark);
+ if (updates == NULL)
+ return NULL;
+ timestamp = gdk_frame_clock_get_frame_time (gtk_widget_get_frame_clock (widget));
+
+ gtk_debug_updates_print (updates, NULL, "Painting at %lli", (long long) timestamp);
+
+ node = gsk_renderer_create_render_node (renderer);
+ gsk_render_node_set_name (node, "Debug Updates");
+ gtk_debug_updates_queue_get_extents (updates, &rect);
+ gsk_render_node_set_bounds (node, &(graphene_rect_t) GRAPHENE_RECT_INIT(rect.x, rect.y, rect.width, rect.height));
+
+ cr = gsk_render_node_get_draw_context (node);
+
+ for (l = g_queue_peek_head_link (updates); l != NULL; l = l->next)
+ {
+ draw = l->data;
+ if (timestamp - draw->timestamp < GDK_DRAW_REGION_MIN_DURATION)
+ progress = 0.0;
+ else if (timestamp - draw->timestamp < GDK_DRAW_REGION_MAX_DURATION)
+ progress = (double) (timestamp - draw->timestamp - GDK_DRAW_REGION_MIN_DURATION)
+ / (GDK_DRAW_REGION_MAX_DURATION - GDK_DRAW_REGION_MIN_DURATION);
+ else
+ continue;
+
+ cairo_set_source_rgba (cr, 1, 0, 0, 0.4 * (1 - progress));
+ gtk_debug_updates_print (updates, draw->region, "Painting with progress %g", progress);
+ gdk_cairo_region (cr, draw->region);
+ cairo_fill (cr);
+ }
+
+ cairo_destroy (cr);
+
+ return node;
+}
+